package ink.metoo.fish.game

import com.fasterxml.jackson.annotation.JsonIgnore
import ink.metoo.fish.game.LiarTavernGame.start
import java.time.LocalDateTime
import java.util.*
import java.util.concurrent.ConcurrentHashMap

object LiarTavernGame {

    private val cacheGameMap = ConcurrentHashMap<String, LiarTavernData>()

    private val keysBySizeMap = mapOf(
        "K" to 6,
        "Q" to 6,
        "J" to 6,
        "A" to 6,
        "S" to 2,
    )

    private val baseKeys = arrayOf("K", "Q", "J", "A")

    fun getLiarTavernGame(uuid: String): LiarTavernData {
        return cacheGameMap[uuid] ?: throw LiarTavernGameException(uuid, "Game not found")
    }

    /*@Synchronized
    fun createOrJoin(creator: String): LiarTavernData {
        val gameData = cacheGameMap.values.findLast { it.checkPlayerCanJoin(creator) }
        return if (gameData != null) {
            join(gameData.uuid, creator)
        } else {
            create(creator)
        }
    }*/

    @Synchronized
    fun create(creator: String): LiarTavernData {
        val uuid = UUID.randomUUID().toString()
        val game = LiarTavernData(uuid, creator)
        cacheGameMap[uuid] = game
        return game
    }

    @Synchronized
    fun clear() {
        cacheGameMap.clear()
    }

    fun join(uuid: String, player: String): LiarTavernData {
        val game = getLiarTavernGame(uuid)
        synchronized(game) {
            !game.addPlayer(player)
        }
        return game
    }


    fun start(uuid: String) {
        val game = getLiarTavernGame(uuid)
        synchronized(game) {
            if (game.status != LiarTavernData.GameStatus.WAIT_PLAYER) {
                throw LiarTavernGameException(uuid, "start fail, because game now status ${game.status}")
            }
            game.status = LiarTavernData.GameStatus.IN_PROGRESS
            val activePlayers = game.activePlayers
            val positions = (1..activePlayers.size).shuffled()

            // 初始化位置
            activePlayers.forEachIndexed { index, data ->
                data.position = positions[index]
            }

            refreshPlayerItem(uuid)

            game.addEvent(LiarTavernData.GameEventType.GAME_START)
            game.next()
        }
    }

    private fun refreshPlayerItem(uuid: String) {
        val game = getLiarTavernGame(uuid)
        synchronized(game) {
            if (game.status != LiarTavernData.GameStatus.IN_PROGRESS) {
                throw LiarTavernGameException(uuid, "refreshPlayerItem fail, because game now status ${game.status}")
            }
            // game.round = 0
            val gameAllItems = keysBySizeMap
                .asSequence()
                .flatMap { (key, value) -> (1..value).map { key } }
                .shuffled()
                .chunked(5)
                .toList()
            val record = hashMapOf<String, List<String>>()
            game.activePlayers.forEachIndexed { index, data ->
                data.items = gameAllItems[index]
                record[data.name] = data.items
            }
            val key = baseKeys.random()
            game.key = key
            data class InnerPair(val key: String, var playItems: List<String>?)
            game.addEvent(LiarTavernData.GameEventType.REFRESH_ITEM) {
                InnerPair(key, record[it])
            }
            game.step += 1
        }
    }

    fun bang(uuid: String, player: String) {
        val game = getLiarTavernGame(uuid)
        synchronized(game) {
            if (game.status != LiarTavernData.GameStatus.IN_PROGRESS) {
                throw LiarTavernGameException(uuid, "游戏状态不是进行中, 当前状态为${game.status}")
            }
            val playerData: LiarTavernData.PlayerData = game.players[player]
                ?: throw LiarTavernGameException(uuid, "你不是这个对局的人, 无法操作")

            if (playerData.status == LiarTavernData.PlayerStatus.DEAD) {
                throw LiarTavernGameException(uuid, "$player 您已经死亡，请等待游戏结束")
            }
            // 不是当前玩家操作
            if (game.waitPlayer != player) {
                throw LiarTavernGameException(uuid, "$player 当前无法操作开枪因为还未到你操作")
            }

            val lastProcess =
                game.playerProcessList.lastOrNull() ?: throw LiarTavernGameException(uuid, "还没有人出过牌")

            // bang人BUG修复
            if (lastProcess.step != game.step) {
                throw LiarTavernGameException(uuid, "当前轮次还未有人出过牌, 你在质疑空气？")
            }

            game.addEvent(LiarTavernData.GameEventType.PLAYER_BANG, player) {
                lastProcess
            }

            val notItems = lastProcess.useItems
                .filter {
                    it == game.key || it == "S"
                }

            // 上一家牌无伪造则质疑的人开枪反之当前玩家开枪
            val shortName = if (notItems.size == lastProcess.useItems.size) player else game.preWaitPlayer

            val shortPlayerData = game.players[shortName]!!

            ++shortPlayerData.shootNumber

            // 是否狗带
            val isDeath = (shortPlayerData.shootNumber..6).random() == 6

            game.addEvent(LiarTavernData.GameEventType.PLAYER_SHOOT, shortName) {
                shortPlayerData.shootNumber
            }


            if (isDeath) {
                shortPlayerData.items = emptyList()
                shortPlayerData.status = LiarTavernData.PlayerStatus.DEAD
                game.addEvent(LiarTavernData.GameEventType.PLAYER_DEATH, shortName)

                if (game.checkGameEnd()) {
                    refreshPlayerItem(uuid)
                    game.next()
                }

                return
            }

            if (game.checkGameEnd()) {
                refreshPlayerItem(uuid)
                game.next()
            }

        }
    }


    fun cards(uuid: String, player: String, useCards: Array<String>) {
        val game = getLiarTavernGame(uuid)
        synchronized(game) {
            if (game.status != LiarTavernData.GameStatus.IN_PROGRESS) {
                throw LiarTavernGameException(uuid, "$player cards fail, because game now status ${game.status}")
            }
            if (game.waitPlayer != player) {
                throw LiarTavernGameException(uuid, "$player 未轮到你出牌")
            }
            val playerData = game.players[player] ?: throw LiarTavernGameException(
                uuid,
                "$player 出牌失败, 你不是这个对局的人,"
            )
            // 狗带还想出牌？
            if (playerData.status == LiarTavernData.PlayerStatus.DEAD) {
                throw LiarTavernGameException(uuid, "$player 狗带还想出牌")
            }
            // 不出牌？
            if (useCards.isEmpty()) {
                throw LiarTavernGameException(uuid, "$player 不能出空气")
            }
            // 已经没牌了还要出啥？
            if (playerData.items.isEmpty()) {
                throw LiarTavernGameException(uuid, "$player 已经没牌了还要出啥")
            }
            // 一次最多出3张
            if (useCards.size > 3) {
                throw LiarTavernGameException(uuid, "$player 一次最多出3张")
            }

            val haveItemPlayers = game.activePlayers
                .filter { it.name != player && it.items.isNotEmpty() }

            // 当其他人都没牌了不能出牌了只能质疑了
            if (haveItemPlayers.isEmpty()) {
                throw LiarTavernGameException(uuid, "$player 其他人都没牌了不能出牌, 只能质疑了")
            }

            data class InnerPair(val key: String, var use: Boolean)

            val pn = playerData.items.map { InnerPair(it, false) }
            for (card in useCards) {
                // 出虚空牌？
                val item = pn.findLast { it.key == card && !it.use } ?: throw LiarTavernGameException(
                    uuid,
                    "$player cards fail, because $card not in items"
                )
                item.use = true
            }
            val cpUseCards = useCards.copyOf()
            game.addPlayerProcess(player, cpUseCards.toList())
            game.addEvent(LiarTavernData.GameEventType.PLAYER_PLAY_CARDS, player) {
                if (it == player) {
                    cpUseCards
                } else {
                    cpUseCards.map { "?" }
                }
            }
            // 在给仅存的牌写回去
            playerData.items = pn.filter { !it.use }.map { it.key }
            // 下一个玩家操作
            game.next()
        }
    }

}

class LiarTavernGameException : RuntimeException {
    constructor(uuid: String, reason: String) : this("LT-$uuid: $reason")
    constructor(reason: String) : super(reason)
}


class LiarTavernData(
    val uuid: String,
    val creator: String
) {

    /**
     * 最大玩家数量
     */
    val maxPlayer: Int = 4

    /**
     * 状态
     */
    var status: GameStatus = GameStatus.WAIT_PLAYER

    /**
     * 活跃时间
     */
    @Volatile
    var activeDateTime: LocalDateTime = LocalDateTime.now()

    /**
     * 房间玩家数量
     */
    var players = ConcurrentHashMap<String, PlayerData>()

    /**
     * 轮数
     */
    @Volatile
    var step = 0

    /**
     * 操作数
     */
    @Volatile
    var round = 0

    @get:JsonIgnore
    val activePlayers: List<PlayerData>
        get() = players.values
            .filter {
                it.status == PlayerStatus.ALIVE
            }
            .sortedBy { it.position }

    var events = mutableListOf<GameEvent>()


    var waitPlayer: String? = null

    var preWaitPlayer: String? = null

    /**
     * 当前回合校验牌
     */
    var key: String? = null

    var playerProcessList: MutableList<PlayerProcess> = mutableListOf()

    data class PlayerProcess(val player: String, val useItems: List<String>, var step: Int)

    fun addPlayerProcess(player: String, useItems: List<String>) {
        playerProcessList.add(PlayerProcess(player, useItems, step))
    }

    @Transient
    private val lock = Object()

    init {
        addPlayer(creator)
    }

    fun checkPlayerCanJoin(player: String): Boolean = when (status) {
        GameStatus.WAIT_PLAYER -> (players.size < maxPlayer) and (players[player] == null)
        else -> false
    }

    fun active() {
        activeDateTime = LocalDateTime.now()
    }

    fun next() = synchronized(lock) {
        ++round

        val aps = activePlayers
        var nextPlayerName: String? = null

        // 没牌的人跳过
        for (i in aps.indices) {
            val playerData = aps[round % aps.size]
            if (playerData.items.isEmpty()) {
                ++round
                continue
            }
            nextPlayerName = playerData.name
            break
        }

        val rd = round
        addEvent(GameEventType.WAIT_PLAYER_OPERATE, nextPlayerName) {
            rd
        }
        preWaitPlayer = waitPlayer
        waitPlayer = nextPlayerName
    }


    fun addPlayer(player: String): Boolean = synchronized(lock) {
        if (checkPlayerCanJoin(player)) {
            players[player] = PlayerData(player)
            active()
            addEvent(GameEventType.PLAYER_JOIN, player)
            if (players.size == maxPlayer) {
                start(uuid)
            }
            return true
        }
        return false
    }

    fun removePlayers(player: String) = synchronized(lock) {
        players.remove(player)
        active()
    }

    fun addEvent(type: GameEventType, player: String? = null, dataSelect: (String) -> Any? = {}) {
        val event = GameEvent(type, player)
        event.dataSelect = dataSelect
        events.add(event)
    }

    fun checkGameEnd(): Boolean {
        if (activePlayers.size <= 1) {
            status = GameStatus.GOOD_GAME
            addEvent(GameEventType.GAME_END) {
                activePlayers.lastOrNull()?.name
            }
            return false
        }
        return true
    }

    data class GameEvent(
        val type: GameEventType,
        val player: String? = null,
        val version: Long = System.currentTimeMillis(),
    ) {
        var record: Any? = null

        @JsonIgnore
        var dataSelect: (String) -> Any? = {}
    }

    enum class GameEventType {

        /**
         * 游戏开始
         */
        GAME_START,

        /**
         * 游戏结束
         */
        GAME_END,

        /**
         * 玩家加入
         */
        PLAYER_JOIN,

        /**
         * 玩家离开
         */
        PLAYER_LEAVE,

        /**
         * 等待玩家操作
         */
        WAIT_PLAYER_OPERATE,

        /**
         * 玩家出牌
         */
        PLAYER_PLAY_CARDS,

        /**
         * 玩家质疑
         */
        PLAYER_BANG,

        /**
         * 玩家开枪
         */
        PLAYER_SHOOT,

        /**
         * 全体开枪
         */
        ALL_SHOOT,

        /**
         * 玩家死亡
         */
        PLAYER_DEATH,

        /**
         * 刷新牌
         */
        REFRESH_ITEM,

    }

    enum class GameStatus {

        /**
         * 等待玩家加入
         */
        WAIT_PLAYER,

        /**
         * 进行中
         */
        IN_PROGRESS,

        /**
         * 结束
         */
        GOOD_GAME
    }

    class PlayerData(val name: String) {

        /**
         * 位置
         */
        var position: Int = -1

        /**
         * 牌
         */
        var items: List<String> = emptyList()

        /**
         * 开枪次数
         */
        @Volatile
        var shootNumber: Int = 0

        /**
         * 状态
         */
        var status: PlayerStatus = PlayerStatus.ALIVE

    }

    enum class PlayerStatus {

        /**
         *  活着
         */
        ALIVE,

        /**
         * 死了
         */
        DEAD
    }


}




